/*
 * Copyright 2012 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.vitaliyl.monitoring.server;

import com.google.android.gcm.server.Constants;
import com.google.android.gcm.server.Message;
import com.google.android.gcm.server.MulticastResult;
import com.google.android.gcm.server.Result;
import com.google.android.gcm.server.Sender;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.vitaliyl.monitoring.server.beans.MessageDescriptor;
import org.vitaliyl.monitoring.server.dao.AMessageDAO;

/**
 * Servlet that sends a message to a device.
 * <p>
 * This servlet is invoked by AppEngine's Push Queue mechanism.
 */
@SuppressWarnings("serial")
public class SendMessageServlet extends BaseServlet {

	private static final String EXTRA_SERVER = "extra.server";
	private static final String EXTRA_TEXT = "extra.text";
	private static final String EXTRA_CONNECTION_LIMIT_EXCEEDED = "extra.connection.limit.exceeded";

	private static final String HEADER_QUEUE_COUNT = "X-AppEngine-TaskRetryCount";
	private static final String HEADER_QUEUE_NAME = "X-AppEngine-QueueName";
	private static final int MAX_RETRY = 3;

	static final String PARAMETER_DEVICE = "device";
	static final String PARAMETER_SERVER = "server";
	static final String PARAMETER_MULTICAST = "multicastKey";

	private Sender sender;

	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		sender = newSender(config);
	}

	/**
	 * Creates the {@link Sender} based on the servlet settings.
	 */
	protected Sender newSender(ServletConfig config) {
		String key = (String) config.getServletContext().getAttribute(ApiKeyInitializer.ATTRIBUTE_ACCESS_KEY);
		return new Sender(key);
	}

	/**
	 * Indicates to App Engine that this task should be retried.
	 */
	private void retryTask(HttpServletResponse resp) {
		resp.setStatus(500);
	}

	/**
	 * Indicates to App Engine that this task is done.
	 */
	private void taskDone(HttpServletResponse resp) {
		resp.setStatus(200);
	}

	/**
	 * Processes the request to add a new message.
	 */
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		if (req.getHeader(HEADER_QUEUE_NAME) == null) {
			throw new IOException("Missing header " + HEADER_QUEUE_NAME);
		}
		String retryCountHeader = req.getHeader(HEADER_QUEUE_COUNT);
		logger.fine("retry count: " + retryCountHeader);
		if (retryCountHeader != null) {
			int retryCount = Integer.parseInt(retryCountHeader);
			if (retryCount > MAX_RETRY) {
				logger.severe("Too many retries, dropping task");
				taskDone(resp);
				return;
			}
		}
		/*
		 * String regId = req.getParameter(PARAMETER_DEVICE); if (regId != null)
		 * { sendSingleMessage(regId, resp); return; }
		 */
		String multicastKey = req.getParameter(PARAMETER_MULTICAST);
		String serverStatusKey = req.getParameter(PARAMETER_SERVER);
		if (multicastKey != null) {
			sendMulticastMessage(multicastKey, serverStatusKey, resp);
			return;
		}
		logger.severe("Invalid request!");
		taskDone(resp);
		return;
	}

	/*
	 * private void sendSingleMessage(String regId, HttpServletResponse resp) {
	 * logger.info("Sending message to device " + regId); Message message =
	 * createMessage(); Result result; try { result =
	 * sender.sendNoRetry(message, regId); } catch (IOException e) {
	 * logger.log(Level.SEVERE, "Exception posting " + message, e);
	 * taskDone(resp); return; } if (result == null) { retryTask(resp); return;
	 * } if (result.getMessageId() != null) {
	 * logger.info("Succesfully sent message to device " + regId); String
	 * canonicalRegId = result.getCanonicalRegistrationId(); if (canonicalRegId
	 * != null) { // same device has more than on registration id: update it
	 * logger.finest("canonicalRegId " + canonicalRegId);
	 * Datastore.updateRegistration(regId, canonicalRegId); } } else { String
	 * error = result.getErrorCodeName(); if
	 * (error.equals(Constants.ERROR_NOT_REGISTERED)) { // application has been
	 * removed from device - unregister it Datastore.unregister(regId); } else {
	 * logger.severe("Error sending message to device " + regId + ": " + error);
	 * } } }
	 */

	private void sendMulticastMessage(String multicastKey, String serverStatusKey, HttpServletResponse resp) {
		// Recover registration ids from datastore
		List<String> regIds = Datastore.getMulticast(multicastKey);
		Message message = createMessage(serverStatusKey);
		
		MulticastResult multicastResult;
		try {
			multicastResult = sender.sendNoRetry(message, regIds);
		} catch (IOException e) {
			logger.log(Level.SEVERE, "Exception posting " + message, e);
			multicastDone(resp, multicastKey, serverStatusKey);
			return;
		}
		boolean allDone = true;
		// check if any registration id must be updated
		if (multicastResult.getCanonicalIds() != 0) {
			List<Result> results = multicastResult.getResults();
			for (int i = 0; i < results.size(); i++) {
				String canonicalRegId = results.get(i).getCanonicalRegistrationId();
				if (canonicalRegId != null) {
					String regId = regIds.get(i);
					Datastore.updateRegistration(regId, canonicalRegId);
				}
			}
		}
		if (multicastResult.getFailure() != 0) {
			// there were failures, check if any could be retried
			List<Result> results = multicastResult.getResults();
			List<String> retriableRegIds = new ArrayList<String>();
			for (int i = 0; i < results.size(); i++) {
				String error = results.get(i).getErrorCodeName();
				if (error != null) {
					String regId = regIds.get(i);
					logger.warning("Got error (" + error + ") for regId " + regId);
					if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
						// application has been removed from device - unregister
						// it
						Datastore.unregister(regId);
					}
					if (error.equals(Constants.ERROR_UNAVAILABLE)) {
						retriableRegIds.add(regId);
					}
				}
			}
			if (!retriableRegIds.isEmpty()) {
				// update task
				Datastore.updateMulticast(multicastKey, retriableRegIds);
				allDone = false;
				retryTask(resp);
			}
		}
		if (allDone) {
			multicastDone(resp, multicastKey, serverStatusKey);
		} else {
			retryTask(resp);
		}
	}

	private void multicastDone(HttpServletResponse resp, String encodedKey, String serverStatusKey) {
		Datastore.deleteEntity(encodedKey);
		//Datastore.deleteEntity(serverStatusKey);
		taskDone(resp);
	}

	private Message createMessage(String serverStatusKey) {
		MessageDescriptor md = AMessageDAO.getMessageDescriptor(serverStatusKey);
		//logger.log(Level.INFO,  md.getConnectionsInfo() + " on server: " + md.getServerUrl());
		return new Message.Builder().collapseKey(md.getServerUrl())
			.addData(EXTRA_SERVER, md.getServerUrl())
			.addData(EXTRA_CONNECTION_LIMIT_EXCEEDED, md.getConnectionsInfo())
			.addData(EXTRA_TEXT, md.toString()).build();
	}
}
